Redis 入门指北

前言

这篇文章是团队内部分享 Introduction-to-Redis 之后的总结。不是完整的演讲稿,与演讲的内容有一定出入。

Redis 是什么

Redis 最开始是 Salvatore Sanfilippo 个人开发的项目,09年开发完成,到现在也不过7年时间,目前已经是最流行的键值对存储数据库。Redis 的作者 antirez 对 Redis 的定义是:一种实现了 TCP 协议的,操作抽象数据类型的守护进程。

为什么要学习,使用 Redis

Redis 作为一个开源、支持网络、基于内存、键值对存储的数据库,有以下特点

  • 简单,稳定,却不失灵活
  • 存储结构丰富,包括
    • 字符串类型
    • 散列类型
    • 列表类型
    • 集合类型
    • 有序集合类型
  • 基本只使用内存,可以高速读写

更进一步的,我们也可以钻研一下 Redis 的源代码,甚至加入到这个项目来。Redis 正在持续的开发中,就在昨天,antriz 还在他的博客上发了一篇文章:The first release candidate of Redis 4.0 is out。就我个人来说,当我接触一个工具,我会学会怎么使用它,除此之外我还会想要知道它内部的原理。Redis 是一个很有意思的东西,这个项目时间不长,源代码也不多,而且文档丰富。代码很有价值,深入研读一下 redis 的源代码,造造轮子练习一下,当然是很好玩的,这里就不扩展了,之后有更多积累了再写写 Redis 实现的细节。

Redis 宣言

正式学习前,先来了解一下 Redis 的『内核』吧,antriz 曾经在他的博客上写了几点关于 Redis 的原则

  • Redis 是一种实现了 TCP 协议的,操作抽象数据类型的守护进程,本质上是 DSL(Domain Specific language)

第一点谈到 Redis 定义的数据结构非常重要,不仅仅与 Redis 的数据类型相关联,还关系着整个系统的时间复杂度和空间复杂度。Redis 设计与实现的第一部分就是数据结构和对象,花了7章,分别从简单动态字符串,链表,字典,跳跃表,整数集合,压缩列表,对象来进行剖析。

  • 内存存储是第一位的。因为内存操作很快,保证了 Redis 的性能。Redis 会尝试其他的存储方式,实现持久化等,但主线剧情一定在内存上。

  • 为最基本的 API 提供最基本的数据结构,Redis 会避免直接与 API 层接触。『Keep It Simple, Stupid』

  • 代码像诗一样。我们乐于使用其他的库,但是在 Redis 这个小故事里,我们想尽可能地自己执笔。『Keep It Simple, Stupid』

  • 我们讨厌复杂。设计一个系统的过程就是跟复杂做斗争的过程。一个 feature 最好在1000行以内,保持简单的方式是直接不去考虑这个 feature. 『Keep It Simple, Stupid』

  • 有两种层次的 API,一种是为了分布式 Redis 而诞生的,一种是为了支持多键值而诞生的(更复杂)

  • 把优化性能作为一种爱好

特性

  • all data is in memory(data can be optionally stored on disk)
  • handles huge workloads easily
  • mostly O(1) behavior
  • ideal for write-heavy workloads
  • support for atomic operations
  • support for transactions
  • has pub/sub functionality
  • tons of client libraries for all major languages
  • single thread, uses aync. IO
  • internal scripting with LUA

为什么那么多公司都在使用 Redis 呢,实际上和 redis 的功能类似的软件也不少,比如 Memcached,一句话来概括,因为 redis 简单而且强大,Redis 是单线程的模型,而 Memcached 支持多线程,所以对于多核的单例来说,Memecached 的性能会好一点,但是 Redis 的性能已经足够好了,在绝大多数情况下不会有性能瓶颈,除非是数据量太大,IO 会花费一些时间,如果出现瓶颈,一般来自机器内存或网络带宽,而不会来自 CPU。而 Redis 3.0 推出之后,Memcached 几乎所以的功能都成了 Redis 的子集,而且 Redis 自带了集群功能,虽然还不够成熟。基本上 Redis 的性能是非常好的,大多数操作的时间复杂度是常数,支持原子操作,支持事务,发布/订阅的功能,几乎所以流行的语言都有客户端,支持用 lua 做脚本。

Redis 与其他数据库的对比:

compare

Redis 初体验

先简单地尝试一下 Redis

上面有谈到,Redis 有5种基本类型,字符串,列表,集合,散列,有序集合。Redis 可以存储键和5种不同数据类型之间的映射。最简单的,也最基本的是字符串:

Redis 中最基础的概念是键值对:你可以获得符合符合规则类型的键名列表,判断一个键是否存在,删除键,获得键值的数据类型等等

字符串

典型用例:

  1. 增加,减少指定的整数
  2. 增加指定浮点数
  3. 向尾部追加值
  4. 获取字符串长度
  5. 同时获得/设置多个键值
  6. 位操作
1
2
3
4
5
6
7
8
9
$ redis-cli
127.0.0.1:6379> set hello "world"
OK
127.0.0.1:6379> get hello
"world"
127.0.0.1:6379> del hello
(integer) 1
127.0.0.1:6379> get hello
(nil)

这是最简单也最常用的 key,value 的对应,你可以令一个 key 对应一个 value,通过 key 拿到 value 的值,删除这个 key-value,简单来说就是,set 可以设置存储在给定键中的值,get 可以获取给定键中的值,del 删除存储在给定键中的值,

当然你的 value 也可以是整数的形式。可以通过 mget 获得多个键的值。

1
2
3
4
5
6
127.0.0.1:6379> set foo bar
OK
127.0.0.1:6379> getset foo baz
"bar"
127.0.0.1:6379> get foo
"baz"
1
2
3
4
5
6
127.0.0.1:6379> setnx foo bar
(integer) 1
127.0.0.1:6379> setnx foo baz
(integer) 0
127.0.0.1:6379> get foo
"bar"

我们有时候需要一些命令的组合,一般的组合命令在 redis 中已经有原生的实现了,可以直接用这些原子操作。比如 get 了一个值,set 一个新的值,你可以用 getset 方法;如果某个键不存在,才进行 set 操作(setnx 是 Set if Not eXists)

列表

接下来我们讲一下列表,redis 支持链表的操作,这里有几个比较典型的命令,你可以在左右两端进行 push,pop 的操作,时间复杂度是O(n)

rpush, lrange, lindex, lpop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
127.0.0.1:6379> rpush list-key item
(integer) 1
127.0.0.1:6379> rpush list-key item1
(integer) 2
127.0.0.1:6379> rpush list-key item2
(integer) 3
127.0.0.1:6379> lrange list-key 0 -1
1) "item"
2) "item1"
3) "item2"
127.0.0.1:6379> lindex list-key 1
"item1"
127.0.0.1:6379> lpop list-key
"item"
127.0.0.1:6379> lrange list-key 0 -1
1) "item1"
2) "item2"
127.0.0.1:6379> RPOP list-key
"item2"
127.0.0.1:6379> lrange list-key 0 -1
1) "item1"

集合

然后是集合,Redis 的集合和列表都可以存储多个字符串,它们之间的不同在于,列表可以存储多个相同的字符串,而集合通过散列表来保证自己存储的每个字符串都是各不相同的。集合采用的是无序方式存储元素。sadd 可以给集合增加元素,smembers 可以获取包含所有元素的集合,通过 sismember 判断某个元素是不是某个集合里的,使用 srem 移除元素时,会返回被移除元素的数量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
127.0.0.1:6379> sadd set-key item
(integer) 1
127.0.0.1:6379> sadd set-key item1
(integer) 1
127.0.0.1:6379> sadd set-key item2
(integer) 1
127.0.0.1:6379> sadd set-key item
(integer) 0
127.0.0.1:6379> smembers set-key
1) "item"
2) "item2"
3) "item1"
127.0.0.1:6379> sismember set-key item3
(integer) 0
127.0.0.1:6379> sismember set-key item
(integer) 1
127.0.0.1:6379> srem set-key item1
(integer) 1
127.0.0.1:6379> srem set-key item1
(integer) 0
127.0.0.1:6379> smembers set-key
1) "item"
2) "item2"

散列

Redis 的散列可以存储多个键值对之间的映射。散列其实像是一个缩略版的 Redis,看起来也像是关系数据库中的行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
127.0.0.1:6379> hset hash-key sub-key0 value0
(integer) 1
127.0.0.1:6379> hset hash-key sub-key1 value1
(integer) 1
127.0.0.1:6379> hset hash-key sub-key2 value2
(integer) 1
127.0.0.1:6379> hgetall hash-key
1) "sub-key0"
2) "value0"
3) "sub-key1"
4) "value1"
5) "sub-key2"
6) "value2"
127.0.0.1:6379> hdel hash-key sub-key1
(integer) 1
127.0.0.1:6379> hgetall hash-key
1) "sub-key0"
2) "value0"
3) "sub-key2"
4) "value2"

有序集合

有序集合和散列有点像,都存储键值对,但是有序集合的键叫做成员(member),每个成员都是各不相同的,有序集合的值称为分值,分值必须为浮点数。注意,有序集合的键和值跟集合的键和值的位置是相反的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
127.0.0.1:6379> zadd zset-key 728 member1
(integer) 1
127.0.0.1:6379> zadd zset-key 729 member0
(integer) 1
127.0.0.1:6379> zadd zset-key 729 member0
(integer) 0
127.0.0.1:6379> zadd zset-key 730 member2
(integer) 1
127.0.0.1:6379> zrange zset-key 0 -1 withscores
1) "member1"
2) "728"
3) "member0"
4) "729"
5) "member2"
6) "730"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
127.0.0.1:6379> zrangebyscore zset-key 0 800 withscores
1) "member1"
2) "728"
3) "member0"
4) "729"
5) "member2"
6) "730"
127.0.0.1:6379> zrangebyscore zset-key 0 729 withscores
1) "member1"
2) "728"
3) "member0"
4) "729"
127.0.0.1:6379> zrem zset-key member1
(integer) 1
127.0.0.1:6379> zrem zset-key member1
(integer) 0
127.0.0.1:6379> zrange zset-key 0 -1 withscores
1) "member0"
2) "729"
3) "member2"
4) "730"

通过 zrangebyscore,指定分数的范围,就可以获得对应的有序集合。其他的操作和集合,散列的命令类似,这里就不多说了。了解了有序集合是什么,我们就能想到它能做什么了,比如像 hackernews,twitter 这样的页面,就可以用到。

更进一步

简单介绍了 redis 的5种数据类型,字符串,列表,集合,散列,有序集合,我们来说说更好玩的。

Expires

1
2
3
4
5
6
7
8
9
10
127.0.0.1:6379> expire foo 20
(integer) 1
127.0.0.1:6379> ttl foo
(integer) 18
127.0.0.1:6379> ttl foo
(integer) 10
127.0.0.1:6379> ttl foo
(integer) 7
127.0.0.1:6379> ttl foo
(integer) -2

我们在实际的开发中经常会遇到一些有时效的数据,比如限时的优惠活动、缓存、验证码等,这些数据可能过了一定时间就要删除。如果是用关系型数据库,需要一个额外的字段来记录到期的时间,定期检测过期的数据,在 Redis 中可以用 expire 来设置一个键的过期时间,到时间后 Redis 会自动删除

发布/订阅

发布

1
2
127.0.0.1:6379> publish channel1.1 'hello'
(integer) 1

订阅

1
2
3
4
5
6
7
8
127.0.0.1:6379> subscribe channel1.1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel1.1"
3) (integer) 1
1) "message"
2) "channel1.1"
3) "hello"

Redis 提供了一组命令让开发者实现『发布/订阅』模式,订阅者可以订阅一个或多个频道(可以通过 pattern 进行订阅),发布者可以向指定的频道发送消息,所有订阅该频道的订阅者都会收到此消息。需要注意的是,这里发布的消息是不会持久化的。订阅的复杂度是:O(1),发布消息的复杂度是 O(n)

lua 脚本

Redis 的脚本功能在2.6版推出,它允许开发者写 lua 脚本上传到 redis server 端执行,使用 lua 脚本的好处有:

  1. 减少网络开销
  2. 原子操作
  3. 可以复用脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ cat random_list.lua
--[[
Random lpush a list key-value
KEYS[1]:key name
ARGV[1]:ramdom seed value
ARGV[2]:add element count
--]]

math.randomseed(ARGV[1]);
for i=1, ARGV[2], 1 do
redis.call("lpush", KEYS[1], math.random());
end
redis.log(redis.LOG_NOTICE, "lpush " .. KEYS[1]);
return true;
$ redis-cli --eval random_list.lua r_list , 3 6
1
2
3
4
5
6
7
$ redis-cli lrange r_list 0 -1
1) "0.43370221947957865"
2) "0.54186119723220416"
3) "0.26702763618297298"
4) "0.31170834336043723"
5) "0.86367337352767282"
6) "0.7832349621612742"

持久化

  • RDB
  • AOF

Redis 支持两种方式的持久化,一种是 RDB(Relational database) 方式,一种是 AOF(Append-only file) 方式。简单概括一下就是:RDB 是根据指定的规则『定时』将内存中的数据存储在硬盘上,AOF 是指每次执行完命令后,将命令本身记录下来。

RDB

优点:

  • compact, single-file, point-in-time
  • very good for disaster recovery
  • maximizes Redis performances, just fork a child
  • RDB allows faster restarts with big datasets compared to AOF

缺点:

  • is NOT good if you need to minimize the chance of data loss in case Redis stops working
  • Fork() can be time consuming if the dataset is big

RDB 是一个非常紧凑的文件,它保存了 redis 在某个时间点上的数据集。这个时间点你是可以自己定的,比如每小时,每个月第一天,它非常适用于灾难恢复,父进程在保存 RDB 文件的时候,唯一需要做的是fork 出一个子进程,父进程不需要进行 IO 操作。在数据集比较大的情况下,RDB 的恢复速度比 AOF 的恢复速度要快,缺点是有可能丢数据,因为这是一个完全备份,而不是一个增量备份。 如果发生故障,那么你至少会丢几分钟的数据。还有一个缺点是,每次保存 RDB 的时候,Redis 都需要 fork 出一个进程,如果数据量比较大,CPU 时间比较紧张,fork 本身这个操作可能会比较费时,可能会导致暂时的不响应。

AOF

优点:

  • much more durable: you can have different fsync policies
  • AOF log is an append only log
  • automatically rewrite the AOF in background when it gets too big
  • AOF contains a log of all the operations one after the other in an easy to understand and parse format

使用 AOF 你可以让数据持久化更灵活,比如 fsync 的策略,你可以每秒 sync,或者每次写入命令的时候 sync,默认是每秒 sync,这种配置下,redis 还是可以保持良好的性能,就算发生了宕机,最多丢一秒数据,AOF 文件是一个只进行追加操作的日志文件(append only log),如果磁盘满了,redis-check-aof 工具可以修复这种问题。AOF 文件体积过大时,可以自动在后台对 AOF 文件重写。重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合,因为是最小集合,所以容易被人读懂,容易被解析

缺点:

  • usually bigger than the equivalent RDB files
  • AOF can be slower than RDB depending on the exact fsync policy
  • experienced rare bugs in specific commands

缺点是,AOF 的体积一般要比 RDB 的体积大,如果是使用 fsync,使用 AOF 的性能一般要比 RDB 的性能差。还有一个缺点是,有可能出现比较严重的 bug,因为个别命令的原因,导致 AOF 文件在重新载入时,无法将数据集恢复成保存时的原样,比如 BRPOPLPUSH 这种阻塞命令,曾经引起过这样的 bug,这种 bug 当然是比较少的,而 RDB 就基本不会有这样的 bug。

AOF 还是 RDB?

antirez 的建议是两种持久化的功能都用,如果你可以忍受几分钟之内的数据丢失,你可以只使用 RDB 持久化,不推荐只使用 AOF 的方式,因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份,RDB 恢复数据集的速度也要比 AOF 恢复的速度要快。

高可用

  • 复制
  • 哨兵
  • Redis 集群

刚才我们谈到持久化功能,Redis 保证了即使在服务器重启的情况下也不会损失(或少量损失)数据,然而单点的情况还是没有改变,如果这台服务器出现了硬盘故障,就会导致数据丢失,为了避免单点故障,我们需要集群(广义集群,非特指 Redis 集群功能)的功能。

哨兵

  • Monitoring
  • Notification
  • Automatic failover
  • Configuration provider

哨兵是 Redis 在2.8版本推出的功能。主要包括四个方面:

  1. 监控,不断检查主服务器和从服务器是否运作正常
  2. 提醒,当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
  3. 自动故障迁移, 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器。
  4. 提供配置信息,哨兵可以用作客户端的服务发现,他可以高速客户端 master 的地址。Redis Sentinel 也是一个分布式的系统,可以单独起一个哨兵程序,也可以启动一个运行在 sentinel 模式下的 Redis 服务器。即使是使用哨兵的功能,这时的 redis 集群的每个数据库仍然存有集群中的所有数据,从而导致集群的总数据存储量受限于可用存储内存最小的数据库节点,因为 redis 中的所有数据都是基于内存的,所以这个问题会比较突出。 因此 Redis 在3.0版本正式推出了官方支持的集群模式

复制

redis-master-slave

Redis 提供了复制(replication)的功能,可以实现当一台数据库中的数据更新后,自动将更新的数据同步到其他数据库上。 master 数据库可以进行读写操作,当写操作导致数据发生变化的时候,自动将数据同步给从数据库, slave 数据库一般是只读的。一个主数据库可以有多个从数据库,从数据库只能有一个主数据库。从数据库不仅可以接收主数据库的同步数据,自己也可以作为主数据库。需要特别注意的是:开启了复制功能,并且主数据库关闭持久化功能时,一定不要用 Supervisor 以及类似的进程工具自动重启主数据库,否则一旦进行了同步,所有的数据都会被清空。为了提高性能,一般从数据库会启用持久化,主数据库会禁用持久化,从数据库崩溃重启了,主数据库会把数据同步过来。主数据库崩溃了,如果是手工通过从数据库恢复,需要严格按照两个步骤进行:

1 在从数据库中使用 slaveof no one 把从数据库提升为主数据库
2 启动崩溃的主数据库,用 slaveof 把主数据库变为主数据库的从数据库,数据就可以恢复回来。 所以说手工恢复是比较麻烦的,我们可以用 redis 提供的哨兵功能来实现这个过程。

Redis 集群

上面的复制模式,Redis 集群的每个数据库存有集群中的所有数据,集群的总数据存储量受限于可用存储内存最小的数据库节点,形成木桶效应。

以往的 redis 集群,我们可以通过客户端分片来解决,有多个数据库节点的时候,由客户端决定每个键交由哪个数据库节点存储。这样就实现了整个数据库分布在 N 个数据库节点中,每个节点只存放总数据量的1/N。这样你需要扩容的时候,得对数据做手工的迁移,为了保证数据的一致性,还有可能让集群暂时下线,相对比较复杂。客户端分片有很多缺点,维护成本高,增加,移除节点比较繁琐,而且维护相同的分片逻辑成本很大,比如系统中有一个 redis 集群,一套业务系统是 Python 写的,一套的 golang 写的,为了保证分片逻辑的一致性,分片逻辑在两套系统中都得实现,开发成本比较大。于是在 redis cluster 模式推出之前,有一些公司做了 redis 集群的代理模式,比较有名的是 twitter 的 Twemproxy:

twemproxy

Twemproxy 的优点是:

  1. 客户端像连接 Redis 实例一样连接 Twemproxy,不需要改任何的代码逻辑。
  2. 支持无效 Redis 实例的自动删除。
  3. Twemproxy 与 Redis 实例保持连接,减少了客户端与Redis实例的连接数。

也有以下不足:

  1. 由于 Redis 客户端的每个请求都经过 Twemproxy 代理才能到达Redis服务器,这个过程中会产生性能损失,当然只有20%。
  2. 没有友好的监控管理后台界面,不利于运维监控。
  3. 最大的问题是 Twemproxy 无法平滑地增加 Redis 实例。对于运维人员来说,当因为业务需要增加 Redis 实例时工作量很大

但这是很长一段时间内最被广泛使用,稳定性最高的 redis 代理。Codis 可以解决 Twemproxy 的不足,可以平滑增加 redis 实例,这里我就不赘述了,目前没有太多研究,也没有什么经验。

Redis 推出了官方的集群功能,采用 P2P 的模式,完全去中心化。

redis-cluster-workflow

Redis 的工作模式如上图,Redis 集群使用数据分片(sharding)而非一致性哈希(consistency hashing)来实现: 一个 Redis 集群包含 16384 个哈希槽(hash slot), 数据库中的每个键都属于这 16384 个哈希槽的其中一个, 集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和 。如果用户将新节点 D 添加到集群中, 那么集群只需要将节点 A 、B 、 C 中的某些槽移动到节点 D 就可以了。因为将一个哈希槽从一个节点移动到另一个节点不会造成节点阻塞, 所以无论是添加新节点还是移除已存在节点, 又或者改变某个节点包含的哈希槽数量, 都不会造成集群下线。 我们这里假设1负责处理 0 号至 5500 号哈希槽,2负责处理 5501 号至 11000 号哈希槽,3负责处理 11001 号至 16384 号哈希槽。如果节点 2 下线了, 那么集群将无法正常运行, 因为集群找不到节点来处理 5501 号至 11000 号的哈希槽。另一方面, Redis 集群对节点使用了主从复制功能,我们可以给2号设置一个从节点,这种情况就跟 replication 的情况一样了,如果节点 2号的 master 和 slave 都下线,Redis 集群还是会停止工作。

目前 Redis 3.0 的集群方案有以下两个问题:

  • 一个 Redis 实例具备了『数据存储』和『路由重定向』,完全去中心化的设计,部署是很简单,但很难对业务进行无痛升级,如果哪天 Redis 集群出了什么严重的 Bug,只能回滚整个 Redis 集群。

  • 对协议进行了较大的修改,对应的 Redis 客户端也需要升级。

如何使用好 Redis

最后,抛砖引玉,说说我们可以怎么最大程度地利用好 Redis?

example

在首页列出最新的评论

没有 Redis

1
SELECT * FROM foo WHERE ... ORDER BY time DESC LIMIT 10

使用 Redis

1
2
3
4
5
6
7
FUNCTION get_latest_comments(start,num_items):
id_list = redis.lrange("latest.comments",start,start+num_items-1)
IF id_list.length < num_items
id_list = SQL_DB("SELECT ... ORDER BY time LIMIT ...")
END
RETURN id_list
END

比如有这样一类问题,我们需要查询最新的10条数据,这里就抽象为最新的10条评论吧,如果没有 Redis,我们每次都需要从数据库里面查询数据,如果我们用 Redis 的话,可以将最新的评论保存在 Redis 的列表里,有新的评论就 push 进入,当然 push 进 Redis 的列表后也需要写数据库,取最新评论的时候从 Redis 去取就行了。

实时排名相关问题

1
ZADD leaderboard <score> <username>
1
ZRANK leaderboard <username>

Redis 还可以解决跟排名相关的应用,利用 Redis 我们可以高效地实现排序,比如游戏榜单的实时排序,我们可以用一个有序集合来存储,有一个新的数据时,可以用 ZADD 添加数据,需要获得某个用户的排名时,可以用 ZRANK 来获得。 如果只用数据库,比如苹果的 game center,热门的游戏可能有几百万人在玩,如果查数据库的话,实时排名基本就不可能了。

根据用户点赞行为和事件排序的问题

Redis 可以实时对所有数据进行排序,比如 hacker news,通过一定的公式来对所有的文章进行排序。

计数相关问题

1
2
INCR ip:<id>
EXPIRE ip:<id> 60

我相信大家都有过这样的需求:给某个值加一个计数器。有一个跟我们平台相关的例子,比如我们需要统计某个时间段内的注册人数,如果5分钟内有几千个注册用户,那么很有可能是被人攻击了,或者我们要分析某个被频繁请求的 api 是否正常,我们可以统计60秒内的独立 ip 访问次数,如果是恶意的请求,我们可以把这个 ip 加入黑名单。如果是存数据库的话,我们确实可以加一个字段,但这是一个频繁写的操作,会比较影响性能。如果是用 Redis 的话,可以很简单地实现这个功能,而且性能比较高

给定时间内的唯一值

1
SADD page:day1:<page_id> <user_id>
1
SCARD page:day1:<page_id>
1
SISMEMBER page:day1:<page_id> <user_id>

当我们需要判断一个时间段内某个集合内是否已经存在某个值,比如记录今天访问过某个页面的用户,我们可以用一个集合来记录,当一个用户访问了某个页面,我们把这个值记录在集合里,当我们需要知道某个页面的独立用户个数,可以用:SCARD,当我们需要了解某个用户是否访问过某个页面,可以用:SISMEMBER

数据实时分析,反垃圾

像这种实时分析的服务,一般不需要考虑数据的持久化的问题,但你可能需要大量缓存数据。所以我们可以用 redis 进行数据的实时分析。

Pub/Sub

Redis Pub/Sub is very very simple to use, stable, and fast, with support for pattern matching, ability to subscribe/unsubscribe to channels on the run,

这个前面有提到,Redis 的订阅/发布功能非常容易使用,简单的订阅/发布功能可以用 redis

队列

A common usage of Redis as a queue is the Resque library, implemented and popularized by Github’s folks.

Redis 可以当做队列使用。github 的 resque 就是基于 Redis 实现的队列系统,这个东西是 ruby 栈的。

管理 Redis

安全层面

还记得 antirz 的 Redis 宣言吗?以简洁为美。在安全层面,Redis 并没有做太多工作。你可以给 Redis 设置一个密码,只需要更改 redis.conf 文件,修改 requirepass 参数的值。

需要注意的是,Redis 的性能极高,并且输入密码错误后 Redis 并不会主动延迟,所以攻击者可以通过穷举的方式破解 Redis 的密码(一秒钟十几万次)。

管理工具

  • redis-cli
  • phpRedisAdmin
  • Rdbtools

更多资源

推荐三本书

Redis 入门指南

Redis 实战

Redis 设计与实现

一本入门,打开新世界的大门,一本实战,教你怎么使用这门功夫,一本内功心法,教你如何融会贯通。

一些常用链接